<?php
/**
 ***********************************************************************************************************************
 * 访问控制规则处理类
 * @author llq
 ***********************************************************************************************************************
 */
namespace soa\access\model;
/**
 * Class rule
 * @package soa\access\model
 */
class rule
{
    /**
     * 黑白名单规则键值前缀
     */
    const BLACKWHITE_PREFIX = 'access:blackwhite:';

    /**
     * IP频率规则键值前缀
     */
    const LIMIT_IP_PREFIX = 'access:limit:ip:';

    /**
     * CLIENT频率规则键值前缀
     */
    const LIMIT_CLIENT_PREFIX = 'access:limit:client:';

    /**
     * @var string
     */
    private $service;

    /**
     * @var array
     */
    private $black_white_map = [
        'black' => 0,
        'black_segment' => 0,
        'white' => 1,
        'white_segment' => 1
    ];

    /**
     * @param $service
     */
    public function __construct($service)
    {
        if(empty($this->service = $service)) {
            logCritical('Error access service name give !', 'access', 'access');
            die('Error access service name give !');
        }
    }

    /**
     * 添加黑白名单
     * @param $ip
     * @param string $type
     * @return bool|int
     */
    public function addBlackWhiteIP($ip, $type = 'black')
    {
        if(
            empty($ip) ||
            !is_string($ip) ||
            empty($type) ||
            !in_array($type, ['black', 'black_segment', 'white', 'white_segment'])
        ) {
            return false;
        }

        // IP段
        if($type == 'black_segment' || $type == 'white_segment') {
            $hashes = $this->getBlackWhiteBySegment($ip);
            $hashes['segment'] = $this->black_white_map[$type];
            return $this->getRedisService()->hMset(self::BLACKWHITE_PREFIX . $ip, $hashes);
        }

        // IP
        $ip_arr = $this->ipFetch($ip);
        $hashes = $this->getBlackWhiteBySegment($ip_arr['first']);
        $hashes['last_' . $ip_arr['last']] = $this->black_white_map[$type];

        return $this->getRedisService()->hMset(self::BLACKWHITE_PREFIX . $ip_arr['first'], $hashes);
    }

    /**
     * 删除黑白名单
     * @param $ip
     * @param string $type
     * @return bool|int
     */
    public function delBlackWhiteIP($ip, $type = 'black')
    {
        if(
            empty($ip) ||
            !is_string($ip) ||
            empty($type) ||
            !in_array($type, ['black', 'black_segment', 'white', 'white_segment'])
        ) {
            return false;
        }

        if($type == 'black_segment' || $type == 'white_segment') {
            return $this->getRedisService()->hDel(self::BLACKWHITE_PREFIX . $ip, 'segment');
        }

        $ip_arr = $this->ipFetch($ip);
        if(empty($ip_arr)) {
            return false;
        }

        return $this->getRedisService()->hDel(self::BLACKWHITE_PREFIX . $ip_arr['first'], 'last_' . $ip_arr['last']);
    }

    /**
     * 判断IP是否在黑白名单内
     * @param $ip
     * @return array
     */
    public function isBlackWhite($ip)
    {
        $ret = [
            'is_black' => false,
            'is_white' => false
        ];

        if(empty($ip) || !is_string($ip) || empty($ip_arr = $this->ipFetch($ip))) {
            return $ret;
        }

        // 获取数据
        $hashes = $this->getBlackWhiteBySegment($ip_arr['first']);
        //myprint($hashes);
        if(
            (isset($hashes['segment']) && $hashes['segment'] == 0) ||
            (isset($hashes['last_' . $ip_arr['last']]) && $hashes['last_' . $ip_arr['last']] == 0)
        ) {
            $ret['is_black'] = true;
        }

        if(
            (isset($hashes['segment']) && $hashes['segment'] == 1) ||
            (isset($hashes['last_' . $ip_arr['last']]) && $hashes['last_' . $ip_arr['last']] == 1)
        ) {
            $ret['is_white'] = true;
        }

        return $ret;
    }


    /**
     * 根据三段IP获取该段IP的信息
     * @param $segment string 如192.168.1
     * @return array
     */
    public function getBlackWhiteBySegment($segment)
    {
        return $this->getRedisService()->hGetAll(self::BLACKWHITE_PREFIX . $segment) ?: [];
    }


    /**
     * 添加自定义控制频率的IP
     * @param $ip
     * @param string $by
     * @return bool
     */
    public function addLimitIP($ip, $by = 'daily', $cnt = 0)
    {
        if(
            empty($ip) ||
            !is_string($ip) ||
            empty($by) ||
            !in_array($by, ['daily', 'hourly', 'minutely']) ||
            !is_numeric($cnt)
        ) {
            return false;
        }
        $this->getRedisService()->hset(self::LIMIT_IP_PREFIX . $this->service . ':' . $ip, $by, intval($cnt));
        return true;
    }

    /**
     * @param $ip
     * @param string $by
     * @return bool
     */
    public function delLimitIP($ip, $by = 'daily')
    {
        if(
            empty($ip) ||
            !is_string($ip) ||
            empty($by) ||
            !in_array($by, ['daily', 'hourly', 'minutely'])
        ) {
            return false;
        }
        return $this->getRedisService()->hDel(self::LIMIT_IP_PREFIX . $this->service . ':' . $ip, $by);
    }

    /**
     * 添加自定义控制频率的Client
     * @param $client_id
     * @param string $by
     * @return bool
     */
    public function addLimitClient($client_id, $by = 'daily', $cnt = 0)
    {
        if(
            empty($client_id) ||
            !is_string($client_id) ||
            empty($client_id) ||
            !in_array($by, ['daily', 'hourly', 'minutely']) ||
            !is_numeric($cnt)
        ) {
            return false;
        }
        $ret = $this->getRedisService()->hset(self::LIMIT_CLIENT_PREFIX . $this->service . ':' . $client_id, $by, intval($cnt));
        //myprint($this->getRedisService()->hGetAll(self::LIMIT_CLIENT_PREFIX . $this->service . ':' . $client_id));
        return true;
    }

    /**
     * @param $ip
     * @param string $by
     * @return bool
     */
    public function delLimitClient($client, $by = 'daily')
    {
        if(
            empty($client) ||
            !is_string($client) ||
            empty($by) ||
            !in_array($by, ['daily', 'hourly', 'minutely'])
        ) {
            return false;
        }
        return $this->getRedisService()->hDel(self::LIMIT_CLIENT_PREFIX . $this->service . ':' . $client, $by);
    }

    /**
     * @param $ip
     * @return array|null
     */
    public function getLimitIP($ip)
    {
        if(empty($ip) || !is_string($ip)) {
            return null;
        }
        return $this->getRedisService()->hGetAll(self::LIMIT_IP_PREFIX . $this->service . ':' . $ip);
    }

    /**
     * @param $client_id
     * @return array|null
     */
    public function getLimitClient($client_id)
    {
        if(empty($client_id) || !is_string($client_id)) {
            return null;
        }
        return $this->getRedisService()->hGetAll(self::LIMIT_CLIENT_PREFIX . $this->service . ':' . $client_id);
    }


    /**
     * 解析IP
     * @param $ip
     * @return array
     */
    private function ipFetch($ip)
    {
        if(!preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)) {
            return [];
        }
        return [
            'first' => (string)substr($ip, 0, strrpos($ip, '.')),
            'last' => (string)substr($ip, strrpos($ip, '.') + 1, strlen($ip))
        ];
    }

    /**
     * @var \artisan\cache\cacheDriver
     */
    private $redisService;

    /**
     * @return \artisan\cache\cacheDriver
     */
    private function getRedisService()
    {
        if(empty($this->redisService)) {
            $this->redisService = \artisan\cache::connect('firewall');
        }
        return $this->redisService;
    }

}